package com.ybear.ybnetworkutil.http

import android.util.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.ConnectionPool
import okhttp3.HttpUrl
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.json.JSONObject
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import java.net.InetAddress
import java.nio.charset.Charset
import java.util.concurrent.TimeUnit

/**
 * @Author MiWi
 * @Date 2024年11月28日
 * @Description     多服务地址切换管理器
 *
 *
 * # 基础配置 - [HttpManager.HttpServicesConfig]
 *
 * // 优先处理的地址下标
 *     private val priorityIndex = if( BuildChannelEm.isGoogle() ) 1 else 0
 * // 地址服务配置
 *     var httpServicesConfig = HttpManager.HttpServicesConfig.Builder()
 *         // 首次初始化时使用的环境配置
 *         .initDefaultEnvProduct(
 *             MmkvUtil.decodeBoolean(MainUtil.TI_API_IS_PRODUCT_ENV, isProduct ) == true
 *         )
 *         // 正式服地址
 *         .initProduct(
 *             priorityIndex, "https://api.talkin.com.cn/", "https://api.talkin.buzz/"
 *         )
 *         // 测试服地址(api.dev.talkin.buzz 不用设置https)
 *         .initDev(
 *             priorityIndex, "https://api-dev.talkin.com.cn/", "http://api.dev.talkin.buzz/"
 *         )
 *         // 出现以下状态码时，切换服务器地址
 *         // x：表示任意值，eg：4xx（404, 410...）,40x（404, 405...），
 *         // !：表示不包括当前状态码。eg：!429（不包括 429），!50x（不包括 500, 501...）
 *         // 4xx （请求错误, 41x主要为校验/参数/预期存在问题，即便切换地址也没用）
 * //         .initErrorHttpCode( "!41x", "!429", "40x", "5xx" )
 *         // Http返回的状态码
 *         .initErrorHttpCode(  "40x", "5xx" )
 *         // 请求体内的状态码
 *         .initErrorBodyCode(  "4xx", "!500", "5xx" )
 *         // 请求体内的code名称
 *         .bodyCodeName( "code" )
 *         // 编码
 *         .charset( Charset.forName( "UTF-8" ) )
 *         // 首次启动网络检测超时时间（毫秒）
 *         .reachableMilliseconds( 1000 )
 *         // 超时时间
 *         .timeoutAll( 20L, TimeUnit.SECONDS )
 * //        .writeTimeout(...)
 * //        .readTimeout(...)
 *         // 日志输出（只负责输出，不负责判断正式环境是否输出）
 *         .callPrintLog { method, tag, message ->
 *             when( method ) {
 *                 Log.DEBUG -> KLog.d( tag, message )
 *                 Log.INFO -> KLog.i( tag, message )
 *                 Log.WARN -> KLog.w( tag, message )
 *                 Log.ERROR -> KLog.e( tag, message )
 *                 else -> KLog.v( tag, message )
 *             }
 *         }
 *         // 构建配置
 *         .build()
 *
 * # 初始化 - [android.app.Application.onCreate]
 * HttpManager.get().init(
 *             httpServicesConfig,
 *             // 初始化 OkHttpClient
 *             object : (() -> OkHttpClient) {
 *                 override fun invoke(): OkHttpClient {
 *                     return getOkHttpClient()
 *                 }
 *             },
 *             // 初始化 Retrofit
 *             object : (() -> Retrofit?) {
 *                 override fun invoke(): Retrofit? {
 *                     return getRetrofit()
 *                 }
 *             }
 *         )
 *
 * # 获取 Retrofit - Create[Retrofit]
 * retrofit = HttpManager.get().setOnSwitchRetrofitCall { r, url ->
 *     // 监听切换 retrofit 后，更新一次 retrofit
 *     Log.d( "HttpManager", "RetrofitHelper.getRetrofit -> Switch Retrofit to: $url" )
 *     retrofit = r
 * }.setOnSwitchHttpBuildCall { _, scheme, host ->
 *     // builder.addEncodedPathSegment(..)
 *     Log.d( "HttpManager", "RetrofitHelper.getRetrofit -> Http to: $scheme://$host" )
 * }.createRetrofit( getOkHttpClient() ) {
 *     // 其他自定义 Retrofit 配置
 *     // it.addCallAdapterFactory(...)
 * }
 *
 *
 * # 获取 OkHttpClient - Create[OkHttpClient]
 * HttpManager.get().createOkHttpClientBuilder()
 */
class HttpManager {
    class HttpServicesConfig(val builder: Builder) {
        class Builder {
            private val defaultTimeout = 20L
            lateinit var product: MutableList<String>
                private set
            lateinit var dev: MutableList<String>
                private set
            // 指定错误码 http code
            lateinit var errorHttpCode: MutableList<String>
                private set
            // 指定错误码 body code
            lateinit var errorBodyCode: MutableList<String>
                private set
            var charset: Charset = Charset.forName( "UTF-8" )
                private set
            // 请求体内的Code名称（不设置则不处理）。eg: code
            var bodyCodeName: String? = null
                private set
            var priorityIndexProduct: Int = 0
                private set
            var priorityIndexDev: Int = 0
                private set
            var reachableMilliseconds: Int = 1000
                private set
            var connectTimeout: Long = defaultTimeout
                private set
            var readTimeout: Long = defaultTimeout
                private set
            var writeTimeout: Long = defaultTimeout
                private set
            var callTimeout: Long = defaultTimeout
                private set
            var timeUnit: TimeUnit = TimeUnit.SECONDS
                private set
            // 默认使用的环境是否为正式环境（主要用于首次初始化时使用的环境配置）
            var isDefaultEnvProduct = true
            // 打印日志（Log.INFO, TAG, MESSAGE）
            var callPrintLog: ((Int, String, String) -> Unit)? = null

            private fun <T> Array<out T>.toSafeList() : MutableList<T> {
                if( this.isEmpty() ) {
                    throw NullPointerException( "Unable to initialize the empty address list." )
                }
                return this.toMutableList()
            }

            fun initProduct(priorityIndex: Int = 0, vararg url: String) : Builder {
                priorityIndexProduct = priorityIndex
                product = url.toSafeList()
                return this
            }

            fun initDev(priorityIndex: Int = 0, vararg url: String) : Builder {
                priorityIndexDev = priorityIndex
                dev = url.toSafeList()
                return this
            }

            fun initErrorHttpCode(vararg codes: String) : Builder {
                errorHttpCode = codes.toMutableList()
                return this
            }

            fun initErrorBodyCode(vararg codes: String) : Builder {
                errorBodyCode = codes.toMutableList()
                return this
            }

            fun charset(charset: Charset) : Builder {
                this.charset = charset
                return this
            }

            fun bodyCodeName(name: String) : Builder {
                bodyCodeName = name
                return this
            }

            fun initDefaultEnvProduct(isProduct: Boolean) : Builder {
                isDefaultEnvProduct = isProduct
                return this
            }

            fun reachableMilliseconds(timeout:Int) : Builder {
                reachableMilliseconds = timeout
                return this
            }

            fun timeoutAll(timeout:Long, unit:TimeUnit) : Builder {
                return connectTimeout( timeout )
                    .readTimeout( timeout )
                    .writeTimeout( timeout )
                    .callTimeout( timeout )
                    .timeUnit( unit )
            }

            fun connectTimeout(timeout:Long) : Builder {
                connectTimeout = timeout
                return this
            }

            fun readTimeout(timeout:Long) : Builder {
                readTimeout = timeout
                return this
            }

            fun writeTimeout(timeout:Long) : Builder {
                writeTimeout = timeout
                return this
            }

            fun callTimeout(timeout:Long) : Builder {
                callTimeout = timeout
                return this
            }

            fun timeUnit(unit:TimeUnit) : Builder {
                timeUnit = unit
                return this
            }

            fun callPrintLog(call: ((Int, String, String) -> Unit)?) : Builder {
                callPrintLog = call
                return this
            }

            fun build(): HttpServicesConfig = HttpServicesConfig( this )
        }

        fun initDefaultEnvProduct() : Boolean = builder.isDefaultEnvProduct
        fun serviceByProduct() : MutableList<String> = builder.product
        fun serviceByDev() : MutableList<String> = builder.dev
        fun service() : MutableList<String> {
            return if( initDefaultEnvProduct() ) serviceByProduct() else serviceByDev()
        }
        fun priorityBaseUrl() : String {
            val index = if( initDefaultEnvProduct() )
                builder.priorityIndexProduct
            else
                builder.priorityIndexDev
            return service()[ index ]
        }

        fun errorHttpCodes() : MutableList<String> = builder.errorHttpCode
        fun errorBodyCode() : MutableList<String> = builder.errorBodyCode
        fun charset() : Charset = builder.charset
        fun bodyCodeName() : String? = builder.bodyCodeName

        fun reachableMilliseconds() : Int = builder.reachableMilliseconds
        fun connectTimeout() : Long = builder.connectTimeout
        fun readTimeout() : Long = builder.readTimeout
        fun writeTimeout() : Long = builder.writeTimeout
        fun callTimeout() : Long = builder.callTimeout
        fun timeUnit() : TimeUnit = builder.timeUnit
        fun callPrintLog() : ((Int, String, String) -> Unit)? = builder.callPrintLog
    }

    companion object {
        const val TAG = "HttpManage"
        @JvmStatic
        private val i by lazy { HttpManager() }
        @JvmStatic
        fun get() : HttpManager { return i }
    }

    private var nextBaseUrlIndex = 0
    private var onSwitchRetrofitCall : ((Retrofit, String) -> Unit)? = null
    private var onSwitchHttpBuildCall : ((HttpUrl.Builder, String, String) -> Unit)? = null
    private var currentBaseUrl: String? = null

    private lateinit var currentClient: OkHttpClient
    private lateinit var config: HttpServicesConfig
    private lateinit var currentRetrofit: Retrofit

    private fun log(method: Int, message: String) {
        config.callPrintLog()?.invoke( method, TAG, message )
    }

    private fun logD(message: String) { log( Log.DEBUG, message ) }

    private fun logE(message: String) { log( Log.ERROR, message ) }

    /**
     * 请求重试拦截器
     */
    private val onRetryInterceptor = Interceptor { chain ->
        val originalRequest = chain.request()
        val request = originalRequest.newBuilder().url( getHttpUrl( originalRequest ) ).build()

        fun findBodyCode(response: Response) : String? {
            return config.bodyCodeName()?.let { code ->
                // 1. 获取响应体
                val responseBody = try { response.body } catch (e: Exception) { null }
                val source = responseBody?.source()
                // Buffer the entire body.
                source?.request( Long.MAX_VALUE )
                // 2. 解析 JSON 并获取 code
                try {
                    val json = source?.buffer?.clone()?.readString( config.charset() )
                    if( json.isNullOrEmpty() ) throw NullPointerException( "Json buffer is null." )
                    JSONObject( json ).getString( code )
                } catch (e: Exception) {
                    handleException( "Json parsing failure!", e )
                    null // 解析失败或 code 字段不存在
                }
            }
        }
        var response: Response? = null
        try {
            response = chain.proceed( request )
            // 服务器内部code可能会出现问题，所以无法只通过请求成功决策是否切换地址
//                if( response.isSuccessful ) return@Interceptor response
            // 1.处理请求体内的code
            val bodyCode = findBodyCode( response )
            if ( shouldSwitchBaseUrl( response.code, bodyCode ) ) {
                throw IllegalStateException("Invalid request! code: ${response.code}, bodyCode: $bodyCode")
            }
            response
        } catch (e: Exception) {
            handleException( "intercept!", e )
            // 3.网络超时，尝试切换 URL
            switchBaseUrl( currentClient )
            var reResponse: Response? = null
            try {
                // 4.重新构建请求并重试
                reResponse = chain.proceed(
                    request.newBuilder().url( getHttpUrl( request ) ).build()
                )
                return@Interceptor reResponse
            } catch (e: Exception) {
                handleException( "Rebuild intercept!", e )
                // 重构请求失败时，抛出异常
                throw e
            } finally {
                try { reResponse?.close() } catch (_: Exception) { }
            }
        } finally {
            try { response?.close() } catch (_: Exception) { }
        }
    }

    private fun handleException(content: String, e: Exception) {
        val tag = e.javaClass.simpleName
        logE( "onRetryInterceptor -> [ $content ], tag: $tag, message: ${e.message}" )
    }

    /**
     * 检查状态码和请求体内的状态码是否允许切换地址
     */
    private fun shouldSwitchBaseUrl(httpCode: Int, bodyCode: String?): Boolean {
        // 规则错误码。eg：["40x", "!404", "500", "!5xx"]
        val errorHttpCodes = config.errorHttpCodes().toTypedArray()
        // 规则错误码。eg：["40x", "!404", "500", "!5xx"]
        val errorBodyCodes = config.errorBodyCode().toTypedArray()
        // 检查 - HTTP状态码
        val matchesHttpCode = matchesAnyPattern( httpCode.toString(), errorHttpCodes )
        // 检查 - 请求体状态码
        val matchesBodyCode = if( bodyCode.isNullOrEmpty() )
            false
        else
            matchesAnyPattern( bodyCode, errorBodyCodes )

        logD( "matchesAnyPattern -> " +
                "httpCode: $httpCode [Matches: $matchesHttpCode], " +
                "bodyCode: $bodyCode [Matches: $matchesBodyCode], " +
                "errorHttpCodes: ${errorHttpCodes.contentToString()}, " +
                "errorBodyCodes: ${errorBodyCodes.contentToString()}"
        )
        return matchesHttpCode || matchesBodyCode
    }

    /**
     * 检查给定的代码是否与模式数组中的任何模式匹配。
     *
     * 举例配置：
     * // Http返回的状态码
     * .initErrorHttpCode( "40x", "5xx" )
     * // 请求体内的状态码
     * .initErrorBodyCode( "4xx", "!500", "5xx" )
     *
     * 举例结果：
     *   40x or 4xx
     *   响应状态码 400~409 会触发 baseURL 切换。
     *   请求状态码 400~499 会触发 baseURL 切换。
     *
     *   !500 or 5xx
     *   响应状态码 500~599 会触发 baseURL 切换。
     *   请求状态码 501~599 会触发 baseURL 切换（!5xx 优先级更高）
     *
     * @param code          要检查的代码字符串。
     * @param patterns      模式数组，其中每个模式可以是：
     *                      - 完整匹配的字符串（例如，"500"）。
     *                      - 包含 "x" 的字符串，其中 "x" 表示任何数字（例如，"4xx"，"5xx"）。
     *                      - 以 "!" 开头的字符串，表示否定匹配（例如，"!500"）。
     * @return              如果代码与任何模式匹配，则返回 true；否则返回 false。
     */
    private fun matchesAnyPattern(code: String, patterns: Array<String>): Boolean {
        val positivePatterns = patterns
            .filterNot { it.startsWith( "!" ) }
        val negativePatterns = patterns
            .filter { it.startsWith( "!" ) }
            .map { it.substring( 1 ) }

        return positivePatterns.any {
                pattern -> matchesPattern( code, pattern ) // 使用辅助函数进行匹配
        } && negativePatterns.all { pattern ->
            !matchesPattern( code, pattern ) // 对否定模式取反
        }
    }

    /**
     * 辅助函数，用于匹配单个模式
     */
    private fun matchesPattern(code: String, pattern: String): Boolean {
        return when {
            pattern.contains( "x" ) -> {
                pattern.replace( "x", "\\d" ).toRegex().matches( code )
            }
            else -> { code == pattern }
        }
    }

    private fun serviceUrls() : MutableList<String> { return config.service() }

    /**
     * 初始化
     * * 建议在[android.app.Application.onCreate]中使用
     */
    suspend fun initSuspend(
        config: HttpServicesConfig,
        initClient: (() -> OkHttpClient?),
        initRetrofit: (() -> Retrofit?)
    ) {
        this.config = config
        // 更新 Client 实例
        currentClient = initClient.invoke() ?: createOkHttpClientBuilder().build()
        // 更新 Retrofit 实例
        currentRetrofit = initRetrofit.invoke() ?: createRetrofit( currentClient )
        withContext( Dispatchers.IO ) {
            try {
                val byName = InetAddress.getByName( currentRetrofit.baseUrl().host )
                if ( !byName.isReachable( config.reachableMilliseconds() ) ) {
                    switchBaseUrl( currentClient )
                    logD(
                        "preTestNetwork -> Pre-test network failed, switched to: ${currentBaseUrl()}"
                    )
                }
            } catch (e: Exception) {
                switchBaseUrl( currentClient )
                logE( "preTestNetwork -> Pre-test network failed, switched to: " +
                        "${currentBaseUrl()}, error: ${e.message}"
                )
            } finally {
                logD( "preTestNetwork -> Pre-test network finished" )
            }
        }
    }

    /**
     * 初始化
     * * 建议在[android.app.Application.onCreate]中使用
     */
    fun init(
        config: HttpServicesConfig,
        initClient: (() -> OkHttpClient?),
        initRetrofit: (() -> Retrofit?)
    ) {
        CoroutineScope(Dispatchers.IO).launch {
            initSuspend( config, initClient, initRetrofit )
        }
    }

    /**
     * 创建Retrofit
     * @param client                    如果没有需求则默认创建一个[OkHttpClient]
     * @param url                       当前服务地址，如果没有需求则使用默认配置好的url
     * @param customerRetrofitBuild     调用者自定义处理
     */
    @JvmOverloads
    fun createRetrofit(
        client: OkHttpClient = createOkHttpClientBuilder().build(),
        url: String = currentBaseUrl(),
        customerRetrofitBuild: ((Retrofit.Builder) -> Unit)? = null
    ): Retrofit {
        val b = Retrofit.Builder()
            .baseUrl( url )
            .client( client )
            .addConverterFactory( GsonConverterFactory.create() )
            .addCallAdapterFactory( RxJava2CallAdapterFactory.create() )
        logD( "createRetrofitBuild -> Create Retrofit: $url" )
        // 将Retrofit回调给调用者自定义
        customerRetrofitBuild?.invoke( b )
        // 更新 Client 实例
        currentClient = client
        // 更新 Retrofit 实例
        currentRetrofit = b.build()
        logD( "createRetrofit -> url: $url" )
        return currentRetrofit
    }

    fun createOkHttpClientBuilder() : OkHttpClient.Builder {
        val timeUnit = config.timeUnit()
        return OkHttpClient.Builder()
            /* 超时配置 */
            .connectTimeout( config.connectTimeout(), timeUnit )
            .readTimeout( config.readTimeout(), timeUnit )
            .writeTimeout( config.writeTimeout(), timeUnit )
            .callTimeout( config.callTimeout(), timeUnit )
            // 错误重连
            .retryOnConnectionFailure( true )
            // 线程池
            .connectionPool( createConnectionPool() )
            // 添加应用拦截器以处理 URL 切换
            .addInterceptor( onRetryInterceptor )
    }

    /**
     * 服务地址切换后回调
     */
    fun setOnSwitchRetrofitCall(call: ((Retrofit, String) -> Unit)): HttpManager {
        onSwitchRetrofitCall = call
        return this
    }

    /**
     * 服务地址切换后回调
     */
    fun setOnSwitchHttpBuildCall(call: ((HttpUrl.Builder, String, String) -> Unit)): HttpManager {
        onSwitchHttpBuildCall = call
        return this
    }

    /**
     * 获取当前服务地址Url
     */
    fun currentBaseUrl(): String {
        var url = currentBaseUrl
        if( url.isNullOrEmpty() ) {
            // 获取优先处理的地址
            url = config.priorityBaseUrl()
            currentBaseUrl = url
            logD( "currentBaseUrl -> url: $url" )
        }
        return url
    }

    /**
     * 创建连接线程池
     * @param numberOfCores     CPU 核心数
     */
    fun createConnectionPool(
        numberOfCores: Int = Runtime.getRuntime().availableProcessors()
    ): ConnectionPool {
        val poolSize = when {
            numberOfCores >= 8 -> 16    // 高性能设备
            numberOfCores >= 4 -> 8     // 中等性能设备
            else -> 4                   // 低性能设备
        }

        val keepAliveDuration = when {
            numberOfCores >= 8 -> 30    // 高性能设备
            numberOfCores >= 4 -> 15    // 中等性能设备
            else -> 5                   // 低性能设备
        }
        logD( "createConnectionPool -> " +
                "poolSize: $poolSize, " +
                "keepAliveDuration: $keepAliveDuration"
        )
        return ConnectionPool( poolSize, keepAliveDuration.toLong(), TimeUnit.SECONDS )
    }

    private fun getHttpUrl(request: Request) : HttpUrl {
        val scheme = currentRetrofit.baseUrl().scheme
        val host = currentRetrofit.baseUrl().host
        val b = request.url
            .newBuilder()
            .scheme( scheme )
            .host( host )
        onSwitchHttpBuildCall?.invoke( b, scheme, host )
        return b.build()
    }

    /**
     * 切换服务地址
     */
    private fun switchBaseUrl(
        client: OkHttpClient, handlerBuildRetrofit: ((Retrofit.Builder) -> Unit)? = null
    ) {
        val differentStrings = serviceUrls().filter { it != currentBaseUrl() }
        currentBaseUrl = if ( differentStrings.isNotEmpty() ) {
            // 使用取余操作实现循环
            val index = nextBaseUrlIndex % differentStrings.size
            // 更新下标，准备下次循环
            nextBaseUrlIndex++
            differentStrings.getOrElse( index ) { currentBaseUrl() }
        } else {
            serviceUrls().getOrElse( 0 ) { currentBaseUrl() }
        }
        // 获取OkHttpClient 的 dispatcher 并关闭它
        try {
            (currentRetrofit.callFactory() as OkHttpClient).dispatcher.executorService.shutdown()
        } catch (e: Exception) {
            handleException( "Close the last retrofit to failure.", e )
        }

        val currentUrl = currentBaseUrl()
        onSwitchRetrofitCall?.invoke(
            createRetrofit( client, currentUrl, handlerBuildRetrofit ),
            currentUrl
        )
        logD( "switchBaseUrl -> url: ${currentBaseUrl()}" )
    }
}